<?php
	/*
	 *	The KLF Framework
	 *
	 *	@author Kristian Oye <kristianoye@gmail.com> 
	 *	@copyright (C) 2010-2011, Kristian Oye
	 */
	namespace KLF\System
	{
	
		/**
		 *  Class KEnum
		 *
		 *  An enumeration type.
		 *  
		 *  $val  = KEnum::FIRST()
		 *  $val2 = KEnum::SECOND()
		 *  $val3 = new KEnum(KEnum::FIRST(), KEnum::SECOND(), 1);
		 *
		 *  @package KLF\System
		 *  @access public
		 *  @author Kristian Oye <kristianoye@gmail.com>
		 *  @since 0.1
		 */
		use KLF\System\Errors\KArgumentException;

		class KEnum extends KObject
		{
			/**
			 * Possible values for all enum types.
			 * @var unknown_type
			 */
			private static $PossibleValues;
			
			/**
			 * Instance variable storing the friendly name of the value.
			 * @var string
			 */
			protected $EnumValue;
			
			/**
			 * Fetches a list of constants for the derived enum class.
			 * @param string $class
			 */
			private static function Initialize($class)
			{
				if (!isset(KEnum::$PossibleValues[$class]))
				{
					KEnum::$PossibleValues[$class] = array();
					$reflect = new \ReflectionClass($class);
					foreach($reflect->getConstants() as $const => $v)
					{
						KEnum::$PossibleValues[$class][$const] = KEnum::Create($class, $const, $v);
					}
				}
				return KEnum::$PossibleValues[$class];
			}
			
			/**
			 * Creates an instance of an enum value.
			 * 
			 * @param string $class
			 * @param string $name
			 * @param int $val
			 * @return KEnum
			 */
			private static function Create($class, $name, $value)
			{
				if (isset(KEnum::$PossibleValues) && 
					isset(KEnum::$PossibleValues[$class]) && 
					isset(KEnum::$PossibleValues[$class][$name]))
				{
					return KEnum::$PossibleValues[$class][$name]; 
				}
				$reflect = new \ReflectionClass($class);
				$instance = $reflect->newInstance();
				$instance->EnumValue = array($name => $value);
				return $instance;
			}
			
			/**
			 * Method for getting an enum value from the collection by name.
			 * @param string $name
			 * @param unused $args
			 */
			public static function __callStatic($name, $args=array(false))
			{
				$class = get_called_class();
				$values = KEnum::Initialize($class);
				
				if (isset($values[$name]))
				{
					$r = KEnum::Create($class, $name, $values[$name]);
					return (is_array($args) && count($args) > 0 && $args[0] === true ? $r->GetValue() : $r);
				}
					
				throw new Errors\KArgumentException("Enum type $class does not have possible value '$name'");
			}
			
			/**
			 * Returns the underlying value of the enum.
			 * 
			 * @return int
			 */
			public function GetValue()
			{
				foreach($this->EnumValue as $key => $val)
				{
					return $val;
				}
			}
			
			/**
			 * Takes a numeric value and converts it into an enum value.
			 * 
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * @param string $val
			 * @return KEnum
			 * @throws Errors\KArgumentException
			 */
			public static function ParseValue($val)
			{
				$class = get_called_class();
				$values = KEnum::Initialize($class);
				foreach($values as $k => $v)
				{
					if($val === $v) return KEnum::Create($class, $k, $v);
				}
				throw new Errors\KArgumentException("Failed to parse '$val' into type $class");
			}
			
			/**
			 * Takes a string value and converts it into an enum value.
			 * 
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * @param string $val
			 * @return KEnum
			 * @throws Errors\KArgumentException
			 */
			public static function ParseString($str)
			{
				$class = get_called_class();
				$values = KEnum::Initialize($class);
				$className = substr($class, strrpos($class, '\\') + 1);
				
				foreach($values as $k => $v)
				{
					if($str === $k || $str === "${className}::${k}" || $str == "${className}::${k}()")
						return KEnum::Create($class, $k, $v);
				}
				throw new Errors\KArgumentException("Failed to parse '$str' into type $class");
			}
			
			/**
			 * Shorthand method for getting the enum's value
			 * 
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * @return KEnum
			 */
			public function __invoke()
			{
				return $this->GetValue();
			}
			
			/**
			 * Returns the string equivelant of the enum value.
			 */
			public function __toString()
			{
				$class = get_class($this);
				return implode(' | ', array_map(function ($item) use ($class)
				{
					return $class . '::' . $item . '()';
				},
				array_keys($this->EnumValue)));
			}
		}
	}
